package org.nativejpa.support.idgen;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Generate 19 long Id base on java nano time, it is very fast and readable
 *
 * the last number can be specified manually to ensure to get unique PK in cluster evironment
 *
 *
 * NOTE: carefully change system time, if turn back, may cause to generate repeated ID
 *
 *
 * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
 */
public class LongTimeIdGenerator implements IdGenerator{
    /**
     * lock
     */
    private final Lock LOCK = new ReentrantLock();

    /**
     * start time, used to plus Nano time difference to get current time
     */
    private final static Long startMilli = System.currentTimeMillis();

    /**
     * recorde init nano time because of nano time is not based on natual time
     */
    private final static long startNano = System.nanoTime();

    /**
     * record last generated PK, if generated a same PK in extream short time, re-genereated
     */
    private volatile long lastPK = -1;

    private final static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");

    /**
     * last number of long PK, normally, can specified by a node ID in cluster
     */
    private int suffix = 0;

    private static final Map<Integer, LongTimeIdGenerator> instanceMap = new HashMap<Integer, LongTimeIdGenerator>();

    private LongTimeIdGenerator(int suffix) {
        this.suffix = suffix;
    }

    public synchronized static LongTimeIdGenerator getInstance(){
        return getInstance(0);
    }

    /**
     * 单机环境下，应该保证用相同的 suffix
     *
     * 在集群环境中，不同的机器必须提供不同的 suffix 来保证生成的ID的唯一性
     *
     * @param suffix 唯一标识好
     */
    public synchronized static LongTimeIdGenerator getInstance(int suffix){
        LongTimeIdGenerator pkgen;
        if(!instanceMap.containsKey(suffix)){
            pkgen = new LongTimeIdGenerator(suffix);
            instanceMap.put(suffix,pkgen);
        }
        else {
            pkgen = instanceMap.get(suffix);
        }
        return pkgen;
    }

    /**
     * get next long PK, format: 2006111423361344491 <br>
     * yyyyMMddHHmmssSSS + Macro Seconds + suffix
     *
     * @return long PK
     */
    public long nextLongId() {
        LOCK.lock();
        try {
            long newPK;
            do {
                long pastNano = System.nanoTime() - startNano; // nano time
                long milliTime = pastNano / 1000000; // milli second
                long macroTime = (pastNano / 100000) % 10; // fist number of macro time
                newPK = Long.parseLong(dateFormat.format(new Date(startMilli + milliTime)) + macroTime + suffix);
            }
            while (lastPK == newPK);
            lastPK = newPK;
            return newPK;
        }
        finally {
            LOCK.unlock();
        }
    }

    public String nextId() {
        return Long.toString(nextLongId());
    }

    public static void main(String[] args) throws Exception {
        LongTimeIdGenerator pkg = getInstance(0);
        int i = 0;
        long now = System.currentTimeMillis();
        while (i++ < 1000) {
            System.out.println(pkg.nextLongId());
        }
        System.out.println("Time: " + (System.currentTimeMillis() - now));
    }
}

